package block

import (
	"bytes"
	"crypto/sha256"
	"encoding/gob"
	"fmt"
	"log"
	"strconv"
	"time"
)

//区块结构
type Block struct {
	BlockHash     []byte
	Transactions  []*Transaction
	PrevBlockHash []byte
	Timestamp     int64
	Nonce         int //工作量证明算法的计数器
}

//序列化区块
func (b *Block) Serialize() []byte {
	var result bytes.Buffer
	encoder := gob.NewEncoder(&result)
	err := encoder.Encode(b)
	if err != nil {
		log.Panic(err)
	}
	return result.Bytes()
}

//反序列化
func DeserializeBlock(d []byte) *Block {
	var block Block
	decoder := gob.NewDecoder(bytes.NewReader(d))
	err := decoder.Decode(&block)
	if err != nil {
		log.Panic(err)
	}
	return &block
}

//生成区块hash值
func (b *Block) setHash() {

	timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
	headers := bytes.Join([][]byte{b.HashTransactions(), b.PrevBlockHash, timestamp}, []byte{})
	hash := sha256.Sum256(headers)
	b.BlockHash = hash[:]
}

//区块校验
func (i *Block) Validate() bool {
	return NewProofOfWork(i).Validate()
}

func (block *Block) PrintBlock() {
	fmt.Printf("PrevBlockHash:%x\n", block.PrevBlockHash)
	fmt.Printf("BlockHash:%x\n", block.BlockHash)
	for _, tx := range block.Transactions {
		fmt.Printf("block tx:%x\n", tx.Id)
	}
	fmt.Printf("Timestamp:%d\n", block.Timestamp)
	fmt.Printf("Nonce: %d\n", block.Nonce)
	//校验数据
	fmt.Printf("PoW: %s\n", strconv.FormatBool(block.Validate()))

	fmt.Println()
}

//生成新区块
func NewBlock(txs []*Transaction, prevBlockHash []byte) *Block {
	fmt.Println("new block start ...")
	block := &Block{Timestamp: time.Now().Unix(), PrevBlockHash: prevBlockHash, Transactions: txs, BlockHash: []byte{}, Nonce: 0}
	pow := NewProofOfWork(block)

	nonce, hash := pow.Run()
	block.BlockHash = hash[:]
	block.Nonce = nonce
	return block
}

//返回block快的hash
func (b *Block) HashTransactions() []byte {
	var txHashes [][]byte
	var txHash [32]byte
	for _, tx := range b.Transactions {
		txHashes = append(txHashes, tx.Id)
	}
	txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))
	return txHash[:]
}

//创世块方法
func NewGenesisBlock(coinbase *Transaction) *Block {
	return NewBlock([]*Transaction{coinbase}, []byte{})
}
